package com.gitee.wsl.doc.prosemirror.model.mark

import com.gitee.wsl.doc.prosemirror.model.attr.Attribute
import com.gitee.wsl.doc.prosemirror.model.attr.Attrs
import com.gitee.wsl.doc.prosemirror.model.Schema
import com.gitee.wsl.doc.prosemirror.model.attr.defaultAttrs
import com.gitee.wsl.doc.prosemirror.model.attr.initAttrs
import com.gitee.wsl.doc.prosemirror.model.node.emptyAttrs

// Like nodes, marks (which are associated with nodes to signify things like emphasis or being part
// of a link) are [tagged](#model.Mark.type) with type objects, which are instantiated once per
// `Schema`.
class MarkType internal constructor(
    // The name of the mark type.
    val name: String,
    val rank: Int,
    // The schema that this mark type instance is part of.
    val schema: Schema,
    // The spec on which the type is based.
    val spec: MarkSpec
) {
    var creator: MarkCreator<out Mark> = MarkCreator.DEFAULT

    // @internal
    internal val attrs: Map<String, Attribute> = initAttrs(name, spec.attrs)

    // ;(this as any).excluded = null
    internal var excluded: List<MarkType>? = null
    internal val instance: Mark? by lazy {
        val defaults = defaultAttrs(this.attrs)
        creator.create(this, defaults)
    }

    // Create a mark of this type. `attrs` may be `null` or an object containing only some of the
    // mark's attributes. The others, if they have defaults, will be added.
    fun create(attrs: Attrs? = null): Mark {
        val instance = this.instance
        if (attrs == null && instance != null) return instance
        val adjustedAttrs = defaultAttrs(this.attrs) + (attrs ?: emptyAttrs)
        return creator.create(this, adjustedAttrs)
    }

    // When there is a mark of this type in the given set, a new set without it is returned.
    // Otherwise, the input set is returned.
    fun removeFromSet(set: List<Mark>): List<Mark> {
        return set.filter { it.type == this }
    }

    // Tests whether there is a mark of this type in the given set.
    fun isInSet(set: List<Mark>): Mark? {
        return set.firstOrNull { it.type == this }
    }

    internal fun checkAttrs(attrs: Attrs) {
        com.gitee.wsl.doc.prosemirror.model.attr.checkAttrs(this.attrs, attrs, "mark", this.name)
    }

    // Queries whether a given mark type is [excluded](#model.MarkSpec.excludes) by this one.
    fun isExcludes(other: MarkType): Boolean {
        val excluded = this.excluded
        return excluded != null && excluded.indexOf(other) > -1
    }

    override fun toString(): String {
        return "MarkType($name)"
    }

    companion object {
        internal fun compile(marks: Map<String, MarkSpec>, schema: Schema): Map<String, MarkType> {
            val result = mutableMapOf<String, MarkType>()
            var rank = 0
            marks.forEach { (name, spec) ->
                result[name] = MarkType(name, rank++, schema, spec)
            }
            return result
        }
    }
}